import { defineComponent, Teleport, reactive, ref, watch, getCurrentInstance, onMounted, onUpdated, onUnmounted, cloneVNode, VNodeChild, PropType, computed, mergeProps, DefineComponent, FunctionalComponent, normalizeClass, unref } from 'vue'
import { NOOP } from '@vue/shared'
import { unrefElement, MaybeComputedElementRef, MaybeRef } from '@vueuse/core'
import { getComponent, filterEmpty, getSlot, findDOMNode, isValidElement } from '../_util/props-util'
import addEventListener from '../_util/Dom/addEventListener'
import Popup from './Popup'
import type { AlignType, TargetPoint } from '../align/interface'
import placements from '../tooltip/placements'
import Lazy from '../_internal/lazy'
import { useMemo } from '../_util/hooks'

type Type = '' | 'click' | 'contextmenu' | 'focus' | 'hover'
export type ActionType = Type | `${Type} ${Type}`

export default defineComponent({
  name: 'Trigger',
  inheritAttrs: false,
  props: {
    prefixCls: String,
    action: { type: String as PropType<ActionType>, default: '' },
    disabled: Boolean,
    // mask
    mask: Boolean,
    maskClosable: { type: Boolean, default: true },
    maskTransitionName: String,
    // popup
    popup: [String, Number, Object, Array] as PropType<VNodeChild>,
    popupVisible: { type: Boolean, default: undefined },
    popupTransition: [Object, Function] as PropType<DefineComponent | FunctionalComponent>,
    popupTransitionName: { default: 'zoom-ani' },
    popupStyle: [Array, Object, String],
    popupClass: [Array, Object, String],
    popupAlign: Object as PropType<AlignType>,
    popupPoint: Object as PropType<TargetPoint>,
    popupPlacement: { type: String as PropType<keyof typeof placements> },
    popupContainer: [Function, Object] as PropType<MaybeComputedElementRef>,
    builtinPlacements: { type: Object, default: placements },
    // todo
    onPopupVisibleChange: Function,
    // todo
    afterPopupVisibleChange: Function,
    disabledAlign: [Boolean, Object] as PropType<MaybeRef<Boolean>>,
    disabledAlignInTransiting: [Boolean, Object] as PropType<MaybeRef<Boolean>>,
    zIndex: [String, Number],
    // target
    mouseEnterDelay: { default: 0 },
    mouseLeaveDelay: { default: 150 },
    focusDelay: { default: 0 },
    blurDelay: { default: 0 },
    // align
    alignPoint: Boolean
  },
  setup(props, { emit, attrs }) {
    const triggerRef = ref()
    const { proxy: vm } = getCurrentInstance()

    const state = reactive({ point: null })
    const visible = useMemo(() => props.popupVisible)

    onMounted(() => {
      addOutsideHandler()
    })
    onUpdated(() => {
      addOutsideHandler()
    })
    onUnmounted(() => {
      clearDelayTimer()
      clearOutsideHandler()
    })

    function setPopupVisible(v: boolean, event?: MouseEvent) {
      if (props.disabled) return
      if (visible.value != v) {
        if (props.popupVisible === undefined) visible.value = v
        emit('popupVisibleChange', v)
      }
      setPoint(event)
    }
    // 设置 popup 位置
    function setPoint(point: MouseEvent) {
      if (!props.alignPoint || !point) return
      state.point = { pageX: point.pageX, pageY: point.pageY }
    }
    let delayTimer: NodeJS.Timeout
    function delaySetPopupVisible(visible, delay, event) {
      clearDelayTimer()
      if (delay) {
        delayTimer = setTimeout(() => {
          setPopupVisible(visible, event)
          clearDelayTimer()
        }, delay)
      } else {
        setPopupVisible(visible, event)
      }
    }
    function clearDelayTimer() {
      clearTimeout(delayTimer)
    }
    function close() {
      setPopupVisible(false)
    }

    let clickOutsideHandler, ctxmOutsideHandler
    function addOutsideHandler() {
      if (visible.value) {
        if (!clickOutsideHandler && props.action.includes('click')) {
          clickOutsideHandler = addEventListener(document, 'mousedown', onDocumentClick)
        }
        if (!ctxmOutsideHandler && props.action.includes('contextmenu')) {
          clickOutsideHandler = addEventListener(document, 'mousedown', onDocumentClick)
        }
      } else {
        clearOutsideHandler()
      }
    }
    function clearOutsideHandler() {
      clickOutsideHandler?.remove()
      clickOutsideHandler = null
      ctxmOutsideHandler?.remove()
      ctxmOutsideHandler = null
    }
    // click popup outside
    function onDocumentClick(event: Event) {
      if (popupMousedowned) return
      if (props.mask && !props.maskClosable) return
      const root = findDOMNode(vm)
      if (!root.contains(event.target as Node)) {
        close()
      }
    }

    // ========================== Popup ==========================
    let popupMousedowned = false
    const align = computed(() => ({
      ...props.builtinPlacements?.[props.popupPlacement],
      ...(unref(props.popupAlign) as any)
    }))
    function renderPopup() {
      const mouseProps: any = {}
      if (props.action.includes('hover')) {
        mouseProps.onMouseenter = clearDelayTimer
        mouseProps.onMouseleave = onPopupMouseleave
      }
      mouseProps.onMousedown = onPopupMousedown
      mouseProps.onMouseup = onPopupMouseup
      const { prefixCls, popupPlacement } = props
      const _visible = visible.value && !props.disabled
      let placementCls = popupPlacement ? `${prefixCls}-placement-${popupPlacement}` : ''

      const popupPorps = {
        prefixCls,
        visible: _visible,
        point: props.alignPoint ? props.popupPoint || state.point : null,
        action: props.action,
        align,
        getRootDomNode,
        mask: props.mask,
        maskTransitionName: props.maskTransitionName,
        // getContainer,
        popupStyle: props.popupStyle,
        popupClass: normalizeClass([props.popupClass, placementCls]),
        disabledAlign: props.disabledAlign,
        disabledAlignInTransiting: props.disabledAlignInTransiting,
        transition: props.popupTransition,
        transitionName: props.popupTransitionName,
        onAlign: attrs.onPopupAlign || NOOP,
        zIndex: props.zIndex,
        ...mouseProps
      }
      return (
        <Lazy visible={_visible}>
          <Popup {...popupPorps}>{getComponent(vm, 'popup')}</Popup>
        </Lazy>
      )
    }
    function popupContainer() {
      return unrefElement(props.popupContainer) ?? document.body
    }

    // ========================== Event ==========================
    let focused = false
    function onClick(e) {
      setPopupVisible(!visible.value, e)
    }
    function onClickHide() {
      setPopupVisible(false)
    }
    function onContextmenu(e: MouseEvent) {
      e.preventDefault()
      setPopupVisible(true, e)
    }
    function onMouseenter(e) {
      delaySetPopupVisible(true, props.mouseEnterDelay, e)
    }
    function onFocus(e) {
      focused = true
      delaySetPopupVisible(true, props.focusDelay, e)
    }
    function onBlur(e) {
      focused = false
      delaySetPopupVisible(false, props.blurDelay, e)
    }
    // ===================== Popup Event ==========================
    function onPopupMouseleave(e) {
      if (props.action.includes('focus') && focused) return
      delaySetPopupVisible(false, props.mouseLeaveDelay, e)
    }
    function onPopupMousedown() {
      popupMousedowned = true
    }
    function onPopupMouseup() {
      popupMousedowned = false
    }

    function getRootDomNode() {
      return findDOMNode(triggerRef.value)
    }

    return vm => {
      let child = filterEmpty(getSlot(vm))[0]
      child = isValidElement(child) ? child : <span>{child}</span>

      const newProps: any = { ref: triggerRef }
      if (props.action.includes('click')) {
        newProps.onClick = onClick
      }
      if (props.action.includes('contextmenu')) {
        newProps.onContextmenu = onContextmenu
        newProps.onClick = onClickHide
      }
      if (props.action.includes('hover')) {
        newProps.onMouseenter = onMouseenter
        newProps.onMouseleave = onPopupMouseleave
      }
      if (props.action.includes('focus')) {
        newProps.onFocus = onFocus
        newProps.onBlur = onBlur
      }
      const trigger = cloneVNode(child, mergeProps(newProps, attrs), true)
      const popup = triggerRef.value ? <Teleport to={popupContainer()}>{renderPopup()}</Teleport> : undefined
      return [trigger, popup]
    }
  }
})
